﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Windows;
using System.Reflection;

namespace ResourceExtension.Abstract
{
    public abstract class AbstractResourceBehavior<TOptions> : IResourceBehavior<TOptions>
        where TOptions : IResourceOptions
    {
        private Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();
        
        private Dictionary<string, Queue<ManagerContext>> queues = new Dictionary<string, Queue<ManagerContext>>();

        private class ManagerContext
        {
            public ResourceXManager manager;
            public Type resourceType;
            public TOptions options;
            public string assemblyName;
        }

        #region Implementation of IResourceBehavior<TOptions>

        public void UpdateQuery(ResourceXManager manager, Type resourceType, TOptions options)
        {
            string assemblyName = GetAssemblyName(resourceType);

            string filePath;

            try
            {
                filePath = GetFilePath(assemblyName, options);
            }
            catch (NotSupportedException)
            {
                // todo logging exeption
                return;
            }

            Assembly assembly;
            if (assemblies.TryGetValue(filePath, out assembly))
            {
                string query = options.ToQuery();
                if (!manager.Exist(query))
                {
                    string resourceName = GetManifestResourceName(assemblyName,
                                                                  resourceType,
                                                                  options);
                    Stream stream = GetStream(assembly, resourceName);
                    manager.Add(query, stream);
                }

                manager.PrimaryQuery = query;
                UpdateEvent(manager, new BehaviorUpdateArgs(manager));
            }
            else
            {

                ManagerContext context = new ManagerContext
                                             {
                                                 assemblyName = assemblyName,
                                                 manager = manager,
                                                 resourceType = resourceType,
                                                 options = options
                                             };
                Queue<ManagerContext> queue;
                if (queues.TryGetValue(filePath, out queue))
                {
                    queue.Enqueue(context);
                }
                else
                {
                    queue = new Queue<ManagerContext>();
                    queues.Add(filePath, queue);
                    queue.Enqueue(context);

                    WebClient client = new WebClient();
                    client.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
                                                    {
                                                        // todo logging exception
                                                        if (e.Error != null)
                                                            return;
                                                        AssemblyPart part = new AssemblyPart();
                                                        assembly = part.Load(e.Result);
                                                        assemblies.Add(filePath, assembly);
                                                        ProcessingAssembly(assembly, filePath);
                                                    };

                    client.OpenReadAsync(new Uri(filePath));
                }
            }


        }

        private void ProcessingAssembly(Assembly assembly, string path)
        {
            Queue<ManagerContext> queue;
            if (queues.TryGetValue(path, out queue))
            {
                while (queue.Count > 0)
                {
                    ManagerContext context = queue.Dequeue();
                    string query = context.options.ToQuery();
                    if (!context.manager.Exist(query))
                    {
                        string resourceName = GetManifestResourceName(context.assemblyName,
                                                                      context.resourceType,
                                                                      context.options);
                        Stream stream = GetStream(assembly, resourceName);
                        if (stream == null)
                            throw new Exception("ManifestResource not found.");
                        context.manager.Add(query, stream);
                    }

                    context.manager.PrimaryQuery = query;
                    UpdateEvent(context.manager, new BehaviorUpdateArgs(context.manager));
                }
            }
        }

        protected abstract string GetManifestResourceName(String assemblyName, Type resourceType, TOptions options);

        private Stream GetStream(Assembly assembly, string resourceName)
        {
            foreach (string manifestResourceName in assembly.GetManifestResourceNames())
            {
                if (string.Equals(manifestResourceName,resourceName,StringComparison.InvariantCultureIgnoreCase))
                {
                    return assembly.GetManifestResourceStream(manifestResourceName);    
                }
            }

            return null;
        }

        private string GetAssemblyName(Type resourceType)
        {
            string assemblyName = string.Empty;
            string[] chunks = resourceType.Assembly.FullName.Split(',');
            if (chunks != null && chunks.Length > 0)
                assemblyName = chunks[0];
            return assemblyName;
        }

        protected abstract string GetFilePath(string assemblyName, TOptions options);

        public event EventHandler<BehaviorUpdateArgs> UpdateEvent;

        #endregion
    }
}